/*
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.sentry.protocol;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.concurrent.ConcurrentHashMap;

import io.sentry.SpanContext;
import io.sentry.util.Objects;

/**
 * Contexts
 *
 * @since 2021-06-02
 **/
public final class Contexts extends ConcurrentHashMap<String, Object> implements Cloneable {
    private static final long serialVersionUID = 252445813254943011L;

    private @Nullable <T> T toContextType(final @NotNull String key, final @NotNull Class<T> clazz) {
        final Object item = get(key);
        return clazz.isInstance(item) ? clazz.cast(item) : null;
    }

    /**
     * getTrace
     *
     * @return SpanContext
     */
    public @Nullable SpanContext getTrace() {
        return toContextType(SpanContext.TYPE, SpanContext.class);
    }

    /**
     * setTrace
     *
     * @param traceContext traceContext
     */
    public void setTrace(final @Nullable SpanContext traceContext) {
        Objects.requireNonNull(traceContext, "traceContext is required");
        this.put(SpanContext.TYPE, traceContext);
    }

    /**
     * getApp
     *
     * @return App
     */
    public @Nullable App getApp() {
        return toContextType(App.TYPE, App.class);
    }

    /**
     * setApp
     *
     * @param app app
     */
    public void setApp(final @NotNull App app) {
        this.put(App.TYPE, app);
    }

    /**
     * getBrowser
     *
     * @return Browser
     */
    public @Nullable Browser getBrowser() {
        return toContextType(Browser.TYPE, Browser.class);
    }

    /**
     * setBrowser
     *
     * @param browser Browser
     */
    public void setBrowser(final @NotNull Browser browser) {
        this.put(Browser.TYPE, browser);
    }

    /**
     * getDevice
     *
     * @return Device
     */
    public @Nullable Device getDevice() {
        return toContextType(Device.TYPE, Device.class);
    }

    /**
     * setDevice
     *
     * @param device Device
     */
    public void setDevice(final @NotNull Device device) {
        this.put(Device.TYPE, device);
    }

    /**
     * getOperatingSystem
     *
     * @return OperatingSystem
     */
    public @Nullable OperatingSystem getOperatingSystem() {
        return toContextType(OperatingSystem.TYPE, OperatingSystem.class);
    }

    /**
     * setOperatingSystem
     *
     * @param operatingSystem OperatingSystem
     */
    public void setOperatingSystem(final @NotNull OperatingSystem operatingSystem) {
        this.put(OperatingSystem.TYPE, operatingSystem);
    }

    /**
     * getRuntime
     *
     * @return SentryRuntime
     */
    public @Nullable SentryRuntime getRuntime() {
        return toContextType(SentryRuntime.TYPE, SentryRuntime.class);
    }

    /**
     * setRuntime
     *
     * @param runtime SentryRuntime
     */
    public void setRuntime(final @NotNull SentryRuntime runtime) {
        this.put(SentryRuntime.TYPE, runtime);
    }

    /**
     * getGpu
     *
     * @return Gpu
     */
    public @Nullable Gpu getGpu() {
        return toContextType(Gpu.TYPE, Gpu.class);
    }

    /**
     * setGpu
     *
     * @param gpu Gpu
     */
    public void setGpu(final @NotNull Gpu gpu) {
        this.put(Gpu.TYPE, gpu);
    }

    /**
     * clone
     *
     * @return Contexts
     * @throws CloneNotSupportedException
     */
    @Override
    public @NotNull Contexts clone() throws CloneNotSupportedException {
        final Contexts clone = new Contexts();
        for (Entry<String, Object> entry : entrySet()) {
            if (entry != null) {
                Object value = entry.getValue();
                if (App.TYPE.equals(entry.getKey()) && value instanceof App) {
                    clone.setApp(((App) value).clone());
                } else if (Browser.TYPE.equals(entry.getKey()) && value instanceof Browser) {
                    clone.setBrowser(((Browser) value).clone());
                } else if (Device.TYPE.equals(entry.getKey()) && value instanceof Device) {
                    clone.setDevice(((Device) value).clone());
                } else if (OperatingSystem.TYPE.equals(entry.getKey())
                    && value instanceof OperatingSystem) {
                    clone.setOperatingSystem(((OperatingSystem) value).clone());
                } else if (SentryRuntime.TYPE.equals(entry.getKey()) && value instanceof SentryRuntime) {
                    clone.setRuntime(((SentryRuntime) value).clone());
                } else if (Gpu.TYPE.equals(entry.getKey()) && value instanceof Gpu) {
                    clone.setGpu(((Gpu) value).clone());
                } else if (SpanContext.TYPE.equals(entry.getKey()) && value instanceof SpanContext) {
                    clone.setTrace(((SpanContext) value).clone());
                } else {
                    clone.put(entry.getKey(), value);
                }
            }
        }
        return clone;
    }
}
